﻿using System.Diagnostics;
using System.Globalization;
using System.Windows.Forms;
using System.Linq;
using System.Linq.Expressions;

namespace System
{
  [DebuggerStepThrough]
  public static class DateTimeExtensions
  {
    public static string To_String(this DateTime dt)
    {
      return dt.ToString("dd/MMM/yyyy HH:mm", new CultureInfo("EN-US"));
    }
    public static DateTime Val(this DateTimePicker value)
    {
      return value.Value;
    }
    public static DateTime January(this int day, int year)
    {
      return new DateTime(year, 1, day);
    }
    public static DateTime February(this int day, int year)
    {
      return new DateTime(year, 2, day);
    }
    public static DateTime March(this int day, int year)
    {
      return new DateTime(year, 3, day);
    }
    public static DateTime April(this int day, int year)
    {
      return new DateTime(year, 4, day);
    }
    public static DateTime May(this int day, int year)
    {
      return new DateTime(year, 5, day);
    }
    public static DateTime June(this int day, int year)
    {
      return new DateTime(year, 6, day);
    }
    public static DateTime July(this int day, int year)
    {
      return new DateTime(year, 7, day);
    }
    public static DateTime August(this int day, int year)
    {
      return new DateTime(year, 8, day);
    }
    public static DateTime September(this int day, int year)
    {
      return new DateTime(year, 9, day);
    }
    public static DateTime October(this int day, int year)
    {
      return new DateTime(year, 10, day);
    }
    public static DateTime November(this int day, int year)
    {
      return new DateTime(year, 11, day);
    }
    public static DateTime December(this int day, int year)
    {
      return new DateTime(year, 12, day);
    }

    public static DateTime SetTime(this DateTime current, int hour, int minute)
    {
      return SetTime(current, hour, minute, 0, 0);
    }
    public static DateTime SetTime(this DateTime current, int hour, int minute, int second)
    {
      return SetTime(current, hour, minute, second, 0);
    }
    public static DateTime SetTime(this DateTime current, int hour, int minute, int second, int millisecond)
    {
      DateTime atTime = new DateTime(current.Year, current.Month, current.Day, hour, minute, second, millisecond);
      return atTime;
    }
    public static DateTime SetTime(this DateTime current, string time)
    {
      var hour = 0;
      var minute = 0;

      if (time.Contains(":"))
      {
        hour = time.Substring(0, time.IndexOf(":")).ToInt32();
        var rest = time.Substring(time.IndexOf(":") + 1);
        if (rest.Contains(":"))
          minute = rest.Substring(0, rest.IndexOf(":")).ToInt32();
        else
          minute = rest.ToInt32();

        if (hour > 23) hour = 23;
        if (hour < 0) hour = 0;

        if (minute > 59) minute = 59;
        if (minute < 0) minute = 0;

      }

      return SetTime(current, hour, minute, 0, 0);
    }

    public static double UtcOffset
    {
      get { return DateTime.Now.Subtract(DateTime.UtcNow).TotalHours; }
    }
    public static int CalculateAge(this DateTime dateOfBirth)
    {
      return CalculateAge(dateOfBirth, DateTime.Now.Date);
    }
    public static int CalculateAge(this DateTime dateOfBirth, DateTime referenceDate)
    {
      var years = referenceDate.Year - dateOfBirth.Year;
      if (referenceDate.Month < dateOfBirth.Month || (referenceDate.Month == dateOfBirth.Month && referenceDate.Day < dateOfBirth.Day))
        --years;
      return years;
    }
    public static int CountDaysOfMonth(this DateTime date)
    {
      var nextMonth = date.AddMonths(1);
      return new DateTime(nextMonth.Year, nextMonth.Month, 1).AddDays(-1).Day;
    }
    public static DateTime FirstDayOfMonth(this DateTime date)
    {
      return new DateTime(date.Year, date.Month, 1);
    }
    public static DateTime FirstDayOfMonth(this DateTime date, DayOfWeek dayOfWeek)
    {
      var dt = date.FirstDayOfMonth();
      while (dt.DayOfWeek != dayOfWeek)
        dt = dt.AddDays(1);
      return dt;
    }
    public static DateTime LastDayOfMonth(this DateTime date)
    {
      return new DateTime(date.Year, date.Month, CountDaysOfMonth(date));
    }
    public static DateTime LastDayOfMonth(this DateTime date, DayOfWeek dayOfWeek)
    {
      var dt = date.LastDayOfMonth();
      while (dt.DayOfWeek != dayOfWeek)
        dt = dt.AddDays(-1);
      return dt;
    }
    public static bool IsToday(this DateTime dt)
    {
      return (dt.Date == DateTime.Today);
    }
    public static DateTimeOffset ToDateTimeOffset(this DateTime localDateTime)
    {
      return localDateTime.ToDateTimeOffset(null);
    }
    public static DateTimeOffset ToDateTimeOffset(this DateTime localDateTime, TimeZoneInfo localTimeZone)
    {
      localTimeZone = (localTimeZone ?? TimeZoneInfo.Local);

      if (localDateTime.Kind != DateTimeKind.Unspecified)
        localDateTime = new DateTime(localDateTime.Ticks, DateTimeKind.Unspecified);

      return TimeZoneInfo.ConvertTimeToUtc(localDateTime, localTimeZone);
    }
    public static DateTime FirstDayOfWeek(this DateTime date)
    {
      return date.FirstDayOfWeek(null);
    }
    public static DateTime FirstDayOfWeek(this DateTime date, CultureInfo cultureInfo)
    {
      cultureInfo = (cultureInfo ?? CultureInfo.CurrentCulture);

      var firstDayOfWeek = cultureInfo.DateTimeFormat.FirstDayOfWeek;
      while (date.DayOfWeek != firstDayOfWeek)
        date = date.AddDays(-1);

      return date;
    }
    public static DateTime LastDayOfWeek(this DateTime date)
    {
      return date.LastDayOfWeek(null);
    }
    public static DateTime LastDayOfWeek(this DateTime date, CultureInfo cultureInfo)
    {
      return date.FirstDayOfWeek(cultureInfo).AddDays(6);
    }
    public static DateTime WeeksWeekday(this DateTime date, DayOfWeek weekday)
    {
      return date.WeeksWeekday(weekday, null);
    }
    public static DateTime WeeksWeekday(this DateTime date, DayOfWeek weekday, CultureInfo cultureInfo)
    {
      var firstDayOfWeek = date.FirstDayOfWeek(cultureInfo);
      return firstDayOfWeek.NextWeekday(weekday);
    }
    public static DateTime NextWeekday(this DateTime date, DayOfWeek weekday)
    {
      while (date.DayOfWeek != weekday)
        date = date.AddDays(1);
      return date;
    }
    public static DateTime PreviousWeekday(this DateTime date, DayOfWeek weekday)
    {
      while (date.DayOfWeek != weekday)
        date = date.AddDays(-1);
      return date;
    }
    public static bool IsDateEqual(this DateTime date, DateTime dateToCompare)
    {
      return (date.Date == dateToCompare.Date);
    }
    public static bool IsTimeEqual(this DateTime time, DateTime timeToCompare)
    {
      return (time.TimeOfDay == timeToCompare.TimeOfDay);
    }
    public static bool IsWeekend(this DateTime date)
    {
      return DayOfWeek.Saturday.IsIn(date.DayOfWeek) && DayOfWeek.Saturday.IsIn(date.DayOfWeek);
    }
    public static bool IsWeekend(this DateTime date, DayOfWeek[] weekEnds)
    {
      return weekEnds.All(x => x.IsIn(date.DayOfWeek));
    }
    public static DateTime AddWeeks(this DateTime date, int value)
    {
      return date.AddDays(value * 7);
    }
    public static int DaysInYear(int year)
    {
      var first = new DateTime(year, 1, 1);
      var last = new DateTime(year + 1, 1, 1);
      return DaysBetween(first, last);
    }
    public static int DaysInYear(this DateTime date)
    {
      return DaysInYear(date.Year);
    }
    public static int DaysBetween(this DateTime fromDate, DateTime toDate)
    {
      return Convert.ToInt32(toDate.Subtract(fromDate).TotalDays);
    }
    public static bool IsBefore(this DateTime source, DateTime other)
    {
      return source.CompareTo(other) < 0;
    }
    public static bool IsAfter(this DateTime source, DateTime other)
    {
      return source.CompareTo(other) > 0;
    }

    public static DateTime JustDate(this DateTime dateTime)
    {
      return dateTime.Date;
    }
    public static DateTime JustDate(this DateTime? dateTime)
    {
      return dateTime.Val().Date;
    }

    public static string ToShortDateString(this DateTime? dateTime)
    {
      return dateTime.GetValueOrDefault().ToShortDateString();
    }

    //public static Expression<Func<DateTime, DateTime>> JustDate1 = d => d.Date;

  }
}